////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_CONCURRENCY_CRITICAL_SECTION_INL__
#define __WN_CONCURRENCY_CRITICAL_SECTION_INL__

#include "WNCore/inc/WNPlatform.h"
#include "WNCore/inc/WNTypes.h"
#include "WNCore/inc/WNAssert.h"
#include "WNCore/inc/WNSystem.h"

#ifdef _WN_WINDOWS
    #include <windows.h>
#elif defined _WN_LINUX
    #include <pthread.h>
    #include <sys/syscall.h>
    #include <errno.h>

    #include "WNConcurrency/inc/WNThread.h"
#endif

namespace WNConcurrency {
    WN_INLINE WNCriticalSection::WNCriticalSection(WN_UINT32 _spinCount) {
        #ifdef _WN_WINDOWS
            if (_spinCount == 0) {
                InitializeCriticalSection(&mCriticalSection);
            } else {
                InitializeCriticalSectionAndSpinCount(&mCriticalSection, _spinCount);
            }
        #elif defined _WN_LINUX
            mLockCount = 0;
            mRecursionCount = 0;
            mOwningThread = 0;

            if (_spinCount == 0 || WNSystem::LogicalThreadCount() == 1) {
                mSpinCount = 0;
            } else {
                mSpinCount = _spinCount;
            }

            pthread_mutexattr_t attributes;

            WN_RELEASE_ASSERT(pthread_mutexattr_init(&attributes) == 0);
            WN_RELEASE_ASSERT(pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_NORMAL) == 0);
            WN_RELEASE_ASSERT(pthread_mutexattr_setrobust(&attributes, PTHREAD_MUTEX_ROBUST) == 0);
            WN_RELEASE_ASSERT(pthread_mutex_init(&mMutex, &attributes) == 0);
            WN_RELEASE_ASSERT(pthread_mutexattr_destroy(&attributes) == 0);
        #endif
    }

    WN_INLINE WNCriticalSection::~WNCriticalSection() {
        #ifdef _WN_WINDOWS
            DeleteCriticalSection(&mCriticalSection);
        #elif defined _WN_LINUX
            WN_RELEASE_ASSERT(pthread_mutex_destroy(&mMutex) == 0);
        #endif
    }

    WN_INLINE WN_VOID WNCriticalSection::Lock() {
        #ifdef _WN_WINDOWS
            EnterCriticalSection(&mCriticalSection);
        #elif defined _WN_LINUX
            const pid_t currentThread = static_cast<pid_t>(syscall(SYS_gettid));

            if (WNAtomicIncrement(&mLockCount) > 1) {
                if (currentThread != mOwningThread) {
                    for (WN_UINT32 spinCount = 0; spinCount < mSpinCount; spinCount++) {
                        if (WNAtomicCompareSwap(&mLockCount, 1, 0) == 0) {
                            mOwningThread = currentThread;
                            mRecursionCount++;

                            return;
                        }

                        WNThreadYield();
                    }

                    WN_RELEASE_ASSERT(pthread_mutex_lock(&mMutex) == 0);
                }
            }

            mOwningThread = currentThread;
            mRecursionCount++;
        #endif
    }

    WN_INLINE WN_BOOL WNCriticalSection::TryLock() {
        #ifdef _WN_WINDOWS
            return(TryEnterCriticalSection(&mCriticalSection) != 0 ? WN_TRUE : WN_FALSE);
        #elif defined _WN_LINUX
            const pid_t currentThread = static_cast<pid_t>(syscall(SYS_gettid));

            if (mOwningThread == currentThread) {
                WNAtomicIncrement(&mLockCount);
            } else {
                if (WNAtomicCompareSwap(&mLockCount, 1, 0) != 0) {
                    return(WN_FALSE);
                }

                mOwningThread = currentThread;
            }

            mRecursionCount++;

            return(WN_TRUE);
        #endif
    }

    WN_INLINE WN_VOID WNCriticalSection::Unlock() {
        #ifdef _WN_WINDOWS
            LeaveCriticalSection(&mCriticalSection);
        #elif defined _WN_LINUX
            const pid_t currentThread = static_cast<pid_t>(syscall(SYS_gettid));
            const WN_UINT32 currentRecursionCount = --mRecursionCount;

            if (currentRecursionCount == 0) {
                mOwningThread = 0;
            }

            if (WNAtomicDecrement(&mLockCount) > 0) {
                if (currentRecursionCount == 0) {
                    WN_RELEASE_ASSERT(pthread_mutex_unlock(&mMutex) == 0);
                }
            }
        #endif
    }

    WN_INLINE WN_UINT32 WNCriticalSection::SetSpinCount(WN_UINT32 _spinCount) {
        if (WNSystem::LogicalThreadCount() > 1) {
            #ifdef _WN_WINDOWS
                return(SetCriticalSectionSpinCount(&mCriticalSection, _spinCount));
            #elif defined _WN_LINUX
                return(WNAtomicSwap(&mSpinCount, static_cast<WN_ATOM_T>(_spinCount)));
            #endif
        } else {
            return(0);
        }
    }

    WN_INLINE WNCriticalSectionScopeLock::WNCriticalSectionScopeLock(WNCriticalSection& _criticalSection) :
        mLocked(WN_TRUE),
        mCriticalSection(_criticalSection) {
        mCriticalSection.Lock();
    }

    WN_INLINE WNCriticalSectionScopeLock::~WNCriticalSectionScopeLock() {
        if (mLocked) {
            mCriticalSection.Unlock();

            mLocked = WN_FALSE;
        }
    }
}

#endif // __WN_CONCURRENCY_CRITICAL_SECTION_INL__
